import json
import os
import shutil
import time
import zipfile
from zeus.operation_service.app.core.asset import ASSET_PATH
from zeus.operation_service.app.core.asset.asset_exception import AssetError
from zeus.operation_service.app.proxy.asset import AssetProxy
from zeus.operation_service.app.core.framework.common.result_code import AssetOperationResultCode
from zeus.operation_service.app.core.file_util import FileUtil, U_RW, G_READ, O_READ
from vulcanus.log.log import LOGGER


def validate_asset(asset_file):
    """
    :param asset_file: 资产包文件
    :return result: dict()

    校验资产包，返回资产包存放目录，item目录，scene目录
    """
    result = dict()
    time_stamp = str(round(time.time() * 1000))
    temp_file_dir = os.path.join(ASSET_PATH, time_stamp)
    if not os.path.exists(temp_file_dir):
        os.makedirs(temp_file_dir)

    try:
        asset_dir_path = ''
        if not asset_file.filename.endswith(".zip"):
            raise AssetError(AssetOperationResultCode.ERR_ASSETS_CHECK)
        temp_file_name = os.path.join(temp_file_dir, asset_file.filename)

        # 将文件保存到ASSET_PATH的临时路径下
        # with os.fdopen(os.open(temp_file_name, os.O_WRONLY | os.O_CREAT | os.O_EXCL, U_RW), 'wb+') as f:
        #    for chunk in asset_file.chunks():
        #        f.write(chunk)
        asset_file.save(temp_file_name)
        # 解压最外层压缩包
        product_zip_file_path, asset_dir = decompress_asset(temp_file_name)
        result['asset_dir'] = asset_dir
        decompress_product(product_zip_file_path, asset_dir)

        # 解压item和scene压缩包
        for each_file in os.listdir(asset_dir):
            each_file_name = each_file.split('.zip')
            each_zip_file_path = os.path.join(asset_dir, each_file)
            each_zip_file_dir = os.path.join(asset_dir, each_file_name[0])
            os.mkdir(each_zip_file_dir)
            decompress_zip(each_zip_file_path, each_zip_file_dir)
            if each_file.endswith("Items.zip"):
                result['item_dir'] = each_file_name[0]
                asset_dir_path = decompress_item(asset_dir, each_zip_file_dir, result)
            else:
                result['scene_dir'] = each_file_name[0]
            FileUtil.file_remove(each_zip_file_path)
            LOGGER.info(f"decompress {each_file} successfully")
        if asset_dir_path == '':
            LOGGER.error(f"asset_dir_path is null")
            raise AssetError(AssetOperationResultCode.ERR_ASSETS_CHECK)
        # 将存放目录改名为 domainId-name-version
        FileUtil.dir_remove(asset_dir_path)
        tmp_asset_dir = f"{os.path.basename(asset_dir_path)}"
        asset_db = AssetProxy().get_asset_by_asset_dir(tmp_asset_dir)
        if asset_db:
            AssetProxy().del_asset_by_asset_dir(tmp_asset_dir)
        os.rename(asset_dir, asset_dir_path)
        # 更新时删除之前待下载的资产包
        FileUtil.file_remove(os.path.join(ASSET_PATH, 'asset_download', f"{os.path.basename(asset_dir_path)}.zip"))
        result['asset_dir'] = os.path.basename(asset_dir_path)
    except AssetError as asset_exception:
        LOGGER.error(asset_exception)
        # 当抛出的异常不是ERR_ASSET_EXISTS_IN_SERVER，则清理环境
        if asset_exception.error_code != AssetOperationResultCode.ERR_ASSET_EXISTS_IN_SERVER.code:
            process_validate_asset_exception_env(result)
        raise asset_exception
    except Exception as e:
        LOGGER.error(e)
        process_validate_asset_exception_env(result)
        raise e
    finally:
        shutil.rmtree(temp_file_dir)
    return result


def decompress_item(asset_dir, each_zip_file_dir, result: dict):
    version_properties_file = os.path.join(each_zip_file_dir, 'version.properties')
    config_map = get_asset_name(version_properties_file)
    result['name'] = json.dumps(config_map['name'], ensure_ascii=False)
    result['version'] = config_map['version']
    LOGGER.info(f"decompress asset:{config_map}")
    asset_dir_name = config_map['domainId'] + '-' + config_map['name']['en'] + '-' + config_map['version']
    asset_dir_path = os.path.join(os.path.dirname(asset_dir), asset_dir_name)
    # 如果数据库不存在该资产包目录，但服务器上有该目录，则删除该资产包目录，否则抛出资产包已存在异常
    check_asset_dir(asset_dir_path, upload_timestamp=config_map['timestamp'])
    # 更新时间戳
    change_asset_timestamp(version_properties_file)
    return asset_dir_path


def decompress_asset(asset_file_path):
    """
        :param asset_file_path: 上传的压缩包
        :return: 解压后第一层压缩包路径, 产品名目录
    """
    if not zipfile.is_zipfile(asset_file_path):
        LOGGER.error("ERR_ASSETS_CHECK: asset file is not a zip file")
        raise AssetError(AssetOperationResultCode.ERR_ASSETS_CHECK)
    file_dir = os.path.dirname(asset_file_path)
    with zipfile.ZipFile(asset_file_path) as asset_zip:
        asset_dir_number = 0
        for path_name in asset_zip.namelist():
            if "assets/" in path_name and path_name.endswith('.zip'):
                asset_dir_number += 1
                product_zip = path_name
                if asset_dir_number > 1:
                    LOGGER.error("ERR_ASSETS_CHECK: assets dir of asset file more than one")
                    raise AssetError(AssetOperationResultCode.ERR_ASSETS_CHECK)
        if asset_dir_number == 0:
            LOGGER.error("ERR_ASSETS_CHECK: assets dir of asset file not exists")
            raise AssetError(AssetOperationResultCode.ERR_ASSETS_CHECK)
        asset_zip.extract(product_zip, file_dir)

    product_zip_file_path = os.path.join(file_dir, product_zip)
    if not zipfile.is_zipfile(product_zip_file_path):
        LOGGER.error("ERR_ASSETS_CHECK: product file of asset file not a zip file")
        raise AssetError(AssetOperationResultCode.ERR_ASSETS_CHECK)

    asset_dir = os.path.join(ASSET_PATH, os.path.basename(product_zip).split('.zip')[0])
    check_asset_dir(asset_dir)
    os.mkdir(asset_dir)
    LOGGER.info(f"decompress asset {asset_dir} successfully")
    return product_zip_file_path, asset_dir


def decompress_product(product_zip_file_path, asset_dir):
    """
    :param product_zip_file_path: 产品名压缩文件（包含item和scene压缩包）
    :param asset_dir: 解压目录

    解压第二层压缩包到asset_dir，得到item和scene压缩包
    """
    with zipfile.ZipFile(product_zip_file_path) as product_zip_file:
        product_zip_file_number = 0
        item_zip_number = 0
        scene_number = 0
        for content in product_zip_file.namelist():
            product_zip_file_number += 1
            if content.endswith("Items.zip"):
                item_zip_number += 1
                item_zip = content
            elif content.endswith("Scenes.zip"):
                scene_number += 1
                scene_zip = content
            else:
                LOGGER.error("ERR_ASSETS_CHECK: product zip file content error")
                raise AssetError(AssetOperationResultCode.ERR_ASSETS_CHECK)
        if product_zip_file_number != 2 or item_zip_number != 1 or scene_number != 1:
            LOGGER.error("ERR_ASSETS_CHECK: product zip file content error")
            raise AssetError(AssetOperationResultCode.ERR_ASSETS_CHECK)
        product_zip_file.extract(item_zip, asset_dir)
        product_zip_file.extract(scene_zip, asset_dir)


def decompress_zip(zip_file_path, to_file):
    with zipfile.ZipFile(zip_file_path) as zip_file:
        zip_file.extractall(to_file)


def get_asset_name(version_properties):
    config_map = {"version": '', "name_en": '', "name_zh": '', "domainId": '', "timestamp": ''}
    version_properties_fd = os.open(version_properties, os.O_RDONLY, U_RW | G_READ | O_READ)
    with os.fdopen(version_properties_fd, 'r', encoding='utf-8') as f:
        for line in f.readlines():
            line_list = line.strip('\n').split('=')
            if len(line_list) < 2:
                continue
            config_map[line_list[0]] = line_list[1]
    if config_map["version"] == '' or config_map["name_en"] == '' or config_map["name_zh"] == '' or config_map[
        "domainId"] == '':
        raise AssetError(AssetOperationResultCode.ERR_ASSETS_PARAMETER)
    if config_map["timestamp"] == '':
        config_map["timestamp"] = str(int(time.time()))
    config_map['name'] = dict()
    config_map['name']['zh'] = config_map["name_zh"]
    config_map['name']['en'] = config_map["name_en"]
    return config_map


def change_asset_timestamp(version_properties):
    timestamp_found = False
    version_properties_file_fd = os.open(version_properties, os.O_RDONLY,
                                         U_RW | G_READ | O_READ)
    new_version_properties_fd = os.open(f"{version_properties}_tmp", os.O_WRONLY | os.O_CREAT,
                                        U_RW | G_READ | O_READ)
    with os.fdopen(version_properties_file_fd, 'r', encoding='utf-8') as old_file, \
            os.fdopen(new_version_properties_fd, 'w', encoding='utf-8') as new_file:
        for line in old_file.readlines():
            data = line.strip('\n').split('=')
            if data[0] == 'timestamp':
                timestamp_found = True
                new_file.write(f"timestamp={str(int(time.time()))}")
            else:
                new_file.write(line + '\n')
        if not timestamp_found:
            new_file.write(f"timestamp={str(int(time.time()))}")
    FileUtil.file_remove(version_properties)
    FileUtil.rename(f"{version_properties}_tmp", version_properties)


def process_validate_asset_exception_env(result):
    if 'asset_dir' in result.keys():
        asset_dir = os.path.basename(result['asset_dir'])
        asset_info_tmp = AssetProxy().get_asset_by_asset_dir(asset_dir)
        # 移除临时解压目录
        if asset_info_tmp is None:
            LOGGER.warning(f"validate assets error, process env; asset dir: {os.path.basename(result['asset_dir'])}")
            FileUtil.dir_remove(result['asset_dir'])


def check_asset_dir(asset_dir, upload_timestamp=None):
    if not os.path.exists(asset_dir):
        return
    # 如果数据库不存在该数据，但服务器上有该目录，则删除该资产包目录，否则抛出资产包已存在异常
    tmp_asset_dir = os.path.basename(asset_dir)
    asset_info = AssetProxy().get_asset_by_asset_dir(tmp_asset_dir)
    if asset_info:
        if not upload_timestamp:
            raise AssetError(AssetOperationResultCode.ERR_ASSETS_CONFLICT)
        origin_version_properties = os.path.join(asset_dir, asset_info.item_dir, "version.properties")
        version_properties_fd = os.open(origin_version_properties, os.O_RDONLY, U_RW | G_READ | O_READ)
        with os.fdopen(version_properties_fd, 'r', encoding='utf-8') as f:
            for line in f.readlines():
                line_list = line.strip('\n').split('=')
                if len(line_list) < 2:
                    continue
                if line_list[0] == 'timestamp' and line_list[1] == upload_timestamp:
                    return
        raise AssetError(AssetOperationResultCode.ERR_ASSETS_TIMESTAMP)
    else:
        LOGGER.warning(f"{os.path.basename(asset_dir)} exists, but not exists in database")
        raise AssetError(AssetOperationResultCode.ERR_ASSET_EXISTS_IN_SERVER)
